package org.test4j.mock.faking.modifier;

import g_asm.org.objectweb.asm.ClassVisitor;
import g_asm.org.objectweb.asm.ClassWriter;
import g_asm.org.objectweb.asm.MethodVisitor;
import lombok.Getter;
import org.test4j.mock.faking.meta.MethodId;
import org.test4j.mock.faking.util.AsmConstant;

import java.util.HashMap;
import java.util.Map;

import static java.lang.reflect.Modifier.*;
import static org.test4j.mock.faking.modifier.FakeTransformer.notMockType;
import static org.test4j.mock.faking.util.AsmConstant.*;

public class FakeClassModifier extends ClassVisitor {
    static final int METHOD_ACCESS_MASK = 0xFFFF - ABSTRACT - NATIVE;

    String superClassName;

    String classDesc;
    /**
     * key: method desc
     * value: true 静态方法,静态代码块; false 普通方法, 构造函数
     */
    @Getter
    final Map<String, Boolean> fakedMethods = new HashMap<>();

    public FakeClassModifier() {
        super(api_code, new ClassWriter(writeFlags));
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        int modifiedVersion = (version & 0xFFFF) < AsmConstant.V1_8 ? AsmConstant.V1_8 : version;
        cv.visit(modifiedVersion, access, name, signature, superName, interfaces);
        superClassName = superName;
        classDesc = name;
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        if (this.notMockMethod(access)) {
            return super.visitMethod(access, name, desc, signature, exceptions);
        }
        this.fakedMethods.put(MethodId.buildMethodDesc(name, desc), isStatic(access));
        MethodVisitor mw = super.visitMethod(access & METHOD_ACCESS_MASK, name, desc, signature, exceptions);
        return new FakeMethodModifier(this, mw, access, name, desc);
    }

    /**
     * 不可mock方法
     * o - 抽象, native和桥接方法
     * o java.lang.System静态方法
     *
     * @param access
     * @return
     */
    private boolean notMockMethod(int access) {
        if ((access & (ABSTRACT + SYNTHETIC + NATIVE)) != 0) {
            return true;
        } else {
            return notMockType(classDesc);
        }
    }

    /**
     * Returns the content of the class file that was built by this ClassWriter.
     *
     * @return the binary content of the JVMS ClassFile structure that was built by this ClassWriter.
     */
    public byte[] toByteArray() {
        return ((ClassWriter) cv).toByteArray();
    }
}